using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Office.Project.EPMSync.Config.Library;
using System.Data.SqlClient;
using System.Globalization;
using System.Collections;

namespace Microsoft.Office.Project.EPMSync.Service
{
    /// <summary>
    /// Implements AttributeChange in an enumerable collection
    /// </summary>
    public class AttributeChangeCollection : IEnumerable
    {
        private List<AttributeChange> _attributeChangedList;
        private ProjectCollection _projectList;
        private SqlConnection _connectionString;
        private Tracing _tracing;
        private SyncConfig _config;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="connectionString">Sql connection string to database holding EPMSync tables.</param>
        /// <param name="tracing">Pointer to the active tracing component.</param>
        /// <param name="config">Pointer to the active configuration.</param>
        /// <param name="projWebSvc">Connection to the Project web service</param>
        public AttributeChangeCollection(SqlConnection connectionString, Tracing tracing, SyncConfig config, ProjectWS.Project projWebSvc)
        {
            _attributeChangedList = new List<AttributeChange>();
            _connectionString = connectionString;
            _tracing = tracing;
            _config = config;
            _projectList = new ProjectCollection(tracing, projWebSvc);
        }

        /// <summary>
        /// Makes sure the list of projects has been read
        /// </summary>
        /// <returns>True if the project list has been intialized.</returns>
        public bool Initialize()
        {
            if (!_projectList.FillProjectList())
            {
                _tracing.WriteTrace(_tracing.MainSwitch.TraceError, "Unable to read the list of Project Server projects.  Please fix the problem and restart the service.");
                return false;
            }
            return true;
        }

        /// <summary>
        /// Implemented to allow enumeration of the collection
        /// </summary>
        /// <returns>IEnumerator which allows enumeration of the collection.</returns>
        public IEnumerator GetEnumerator()
        {
            return _attributeChangedList.GetEnumerator();
        }

        /// <summary>
        /// Sets the processing state flag for the selected project based on the current detected state.  If the project is
        /// not in the detected state, the state of the project is not changed.
        /// </summary>
        /// <param name="projectUid">Unique Id of the project</param>
        /// <param name="startState">The starting state of the project.</param>
        /// <param name="finishState">The desired state of the project.</param>
        public void UpdateProjectState(Guid projectUid, ChangeState startState, ChangeState finishState)
        {
            if (_attributeChangedList != null)
            {
                foreach (AttributeChange ac in _attributeChangedList)
                {
                    if ((ac.ChangeStatus == startState) && (projectUid == _projectList.getProjectID(ac.ProjectName)))
                    {
                        if (_config.IsProjectExcluded(projectUid))
                            ac.ChangeStatus = ChangeState.PSUpdateComplete;
                        else
                            ac.ChangeStatus = finishState;
                    }
                }
            }
        }

        /// <summary>
        /// Sets the processing state flag for the selected project
        /// </summary>
        /// <param name="projectUid">Unique Id of the project</param>
        /// <param name="finishState">The desired state of the project.</param>
        public void UpdateProjectState(Guid projectUid, ChangeState finishState)
        {
            if (_attributeChangedList != null)
            {
                foreach (AttributeChange ac in _attributeChangedList)
                {
                    if (projectUid == _projectList.getProjectID(ac.ProjectName))
                    {
                        if (_config.IsProjectExcluded(projectUid))
                            ac.ChangeStatus = ChangeState.PSUpdateComplete;
                        else
                            ac.ChangeStatus = finishState;
                    }
                }
            }
        }

        /// <summary>
        /// Sets the processing state flag for all projects based on the current detected state.  If a project is
        /// not in the detected state, the state of the project is not changed.
        /// </summary>
        /// <param name="projectUid">Unique Id of the project</param>
        /// <param name="startState">The starting state of the project.</param>
        /// <param name="finishState">The desired state of the project.</param>
        public void UpdateAllProjectStates(ChangeState startState, ChangeState finishState)
        {
            if (_attributeChangedList != null)
            {
                foreach (AttributeChange ac in _attributeChangedList)
                {
                    if (ac.ChangeStatus == startState)
                    {
                        ac.ChangeStatus = finishState;
                    }
                }
            }
        }

        /// <summary>
        /// Checks if there are any items in the collection that were detected but have yet to be processed.
        /// </summary>
        public bool ItemsDetectedNotProcessed
        {
            get
            {
                if (_attributeChangedList != null)
                {
                    foreach (AttributeChange ac in _attributeChangedList)
                    {
                        if (ac.ChangeStatus == ChangeState.Detected)
                            return true;
                    }
                }
                return false;
            }
        }

        /// <summary>
        /// Returns the Guid of the first project it finds that is in the detected state.
        /// </summary>
        public Guid GetNextDetectedProject
        {
            get
            {
                if (_attributeChangedList != null)
                {
                    foreach (AttributeChange ac in _attributeChangedList)
                    {
                        if (ac.ChangeStatus == ChangeState.Detected)
                            return ac.ProjectUid;
                    }
                }
                return Guid.Empty;
            }
        }

        #region Database Methods

        /// <summary>
        /// Read all the attribute events that have fired for processing.
        /// </summary>
        /// <returns>The count of attribute events detected</returns>
        public int Fill()
        {
            int countChanges = 0;

            if (_attributeChangedList != null)
                _attributeChangedList.Clear();

            // Query the database for a list of changes
            string queryString = String.Format(CultureInfo.InvariantCulture, "SELECT * FROM esQUEUE_ATTRIBUTE_EVENTS WHERE AccountID = {0}", _config.AccountID);
            SqlCommand command = new SqlCommand(queryString, _connectionString);
            SqlDataReader reader = command.ExecuteReader();

            if (reader.HasRows)
            {
                // Create a list of the necessary changes
                while (reader.Read())
                {
                    _attributeChangedList.Add(new AttributeChange(reader.GetInt32(0), reader.GetInt32(1), reader.GetInt32(2), reader.GetInt32(3),
                        reader.GetInt32(4), reader.GetString(5), _projectList.getProjectID(reader.GetString(5)), reader.GetString(6), reader.GetString(7)));
                    countChanges++;
                }

                _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, String.Format(CultureInfo.InvariantCulture, "Detected {0} changes to be sync'd to Project Server", countChanges.ToString(CultureInfo.InvariantCulture)));
            }

            reader.Close();
            return countChanges;
        }

        /// <summary>
        /// Cycles through collection to remove processed change from the attribute event list in the database.
        /// </summary>
        public void RemoveCompletedChanges()
        {
            SqlCommand cmdDelete;
            SqlCommand cmdInsert;
            List<int> ItemsToRemove = new List<int>();

            _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, "Removing completed changes from database");

            // Remove from the database
            foreach (AttributeChange ac in _attributeChangedList)
            {
                if (ac.ChangeStatus == ChangeState.MoveToArchive)
                {

                    _tracing.WriteTrace(_tracing.MainSwitch.TraceInfo, String.Format(CultureInfo.InvariantCulture, "Moving change [{0}] to archive for project [{1}]", ac.AttributeId.ToString(CultureInfo.InvariantCulture), ac.ProjectName));
                    cmdInsert = new SqlCommand(String.Format(CultureInfo.InvariantCulture, "INSERT esQUEUE_ATTRIBUTE_EVENTS_ARCHIVE VALUES ({0},{1},{2},{3},'{4}','{5}','{6}')",
                        ac.EntityId.ToString(CultureInfo.InvariantCulture), ac.AttributeId.ToString(CultureInfo.InvariantCulture),
                        ac.DataType.ToString(CultureInfo.InvariantCulture), ac.AccountId.ToString(CultureInfo.InvariantCulture),
                        ac.ProjectName, ac.OldValue, ac.NewValue), _connectionString);
                    try
                    {
                        if (cmdInsert.ExecuteNonQuery() == 1)
                            ac.ChangeStatus = ChangeState.PSUpdateComplete;
                        else
                            ac.ChangeStatus = ChangeState.Error;
                    }
                    catch (SqlException ex)
                    {
                        _tracing.WriteTrace(_tracing.MainSwitch.TraceError, String.Format(CultureInfo.InvariantCulture, "Failed to move change [{0}] to archive for project [{1}], exception: {2}",
                            ac.AttributeId.ToString(CultureInfo.InvariantCulture), ac.ProjectName, ex.Message));
                        ac.ChangeStatus = ChangeState.Error;
                    }
                }

                if (ac.ChangeStatus == ChangeState.PSUpdateComplete)
                {
                    cmdDelete = new SqlCommand(String.Format(CultureInfo.InvariantCulture, "DELETE FROM esQUEUE_ATTRIBUTE_EVENTS WHERE ID = {0}", ac.Id), _connectionString);
                    if (cmdDelete.ExecuteNonQuery() == 1)
                    {
                        ac.ChangeStatus = ChangeState.RemoveFromList;
                        ItemsToRemove.Add(_attributeChangedList.IndexOf(ac));
                    }
                    else
                    {
                        ac.ChangeStatus = ChangeState.Error;
                    }
                }
            }

            // Remove from the list
            ItemsToRemove.Sort();
            ItemsToRemove.Reverse();
            foreach (int x in ItemsToRemove)
            {
                _attributeChangedList.RemoveAt(x);
            }

        }

        #endregion

    }
}
